package cn.xhh.dailyreview.client;

import cn.xhh.dailyreview.client.utils.ClientUtil;
import cn.xhh.dailyreview.client.utils.MsgCallback;
import cn.xhh.dailyreview.client.utils.Result;
import cn.xhh.dailyreview.client.utils.Token;
import cn.xhh.dailyreview.client.utils.Result.Status;

import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.ButtonEvent;
import com.extjs.gxt.ui.client.event.ComponentEvent;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.KeyListener;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.event.MessageBoxEvent;
import com.extjs.gxt.ui.client.event.SelectionListener;
import com.extjs.gxt.ui.client.widget.LayoutContainer;
import com.extjs.gxt.ui.client.widget.MessageBox;
import com.extjs.gxt.ui.client.widget.button.Button;
import com.extjs.gxt.ui.client.widget.form.FormPanel;
import com.extjs.gxt.ui.client.widget.form.TextField;
import com.extjs.gxt.ui.client.widget.layout.HBoxLayoutData;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Hyperlink;

/**
 * The login panel showing the form for the user to sign in.
 * @author cooky
 */
public class LoginPanel {
	
	private final DailyReview dailyReview;
	
	private FormPanel loginForm;
	/** submit an application by entering an email address */
	private FormPanel regSubmitForm;
	/** show by clicking the link in the notification email, 
	 * for filling out password to complete the registration */
	private FormPanel regProcessForm;
	private FormPanel forgotPwdSubmitForm;

	/**
	 * @param email email for confirmation
	 * @param code code for confirmation
	 */
	public LoginPanel(final DailyReview dailyReview) {
		this.dailyReview = dailyReview;
	}
	
	public void show(final Token token, final String email, final String code) {
		if ((Token.confirm==token || Token.reset_pwd==token) 
				&& token!=null && email!=null && code!=null) { // for confirming registration
			if (!email.matches(ClientUtil.REG_EXP_EMAIL)) {
				ClientUtil.alert("Wrong email or code. Link broken?");
				showLoginForm();
				return;
			}
			AsyncCallback<Result> acCheckConfirm = new MsgCallback(dailyReview) {
				@Override
				public void onSuccess(Result result) {
					clearLoading();
					if (result.successful()) {
						if (regProcessForm == null) initRegProcessForm(email, code);
						if (Token.reset_pwd == token) regProcessForm.setHeading("Setup New Password");
						showForm(regProcessForm);
					} else {
						showLoginForm();
						if (Status.FAILURE == result.status)
							ClientUtil.alert("Wrong email or code. Link broken?");
						else if (Status.OTHER_A == result.status)
							ClientUtil.alert("User does not exist. Register again if confirming too late.");
						else super.onSuccess(result);
					}
				}
			};
			dailyReview.showLoading();
			ClientUtil.getService().checkSetup(email, code, acCheckConfirm);
		} else { // show the login form
			showLoginForm();
		}
	}
	
	private void configOptions() {
		final LayoutContainer options = dailyReview.getOptions();
		options.removeAll();
		
		Hyperlink lnkRegister = new Hyperlink();
		lnkRegister.setText("Register");
		lnkRegister.addStyleName("login-options");
		lnkRegister.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				if (regSubmitForm == null) initRegSubmitForm();
				showForm(regSubmitForm);
			}
		});
		options.add(lnkRegister, new HBoxLayoutData(0, 10, 0, 0));
		
		Hyperlink lnkForgotPwd = new Hyperlink();
		lnkForgotPwd.setText("Forgot password?");
		lnkForgotPwd.addStyleName("login-options");
		lnkForgotPwd.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				if (forgotPwdSubmitForm == null) initForgotPwdSubmitForm();
				showForm(forgotPwdSubmitForm);
			}
		});
		options.add(lnkForgotPwd);
	}

	private void initLoginForm() {
		loginForm = new FormPanel();
		loginForm.setHeading("LOGIN");
		loginForm.setWidth(340);
		loginForm.setFrame(true);
		
		final TextField<String> tfEmail = new TextField<String>();
		tfEmail.setFieldLabel("Email");
		tfEmail.setAllowBlank(false);
		loginForm.add(tfEmail);
		
		final TextField<String> tfPasswd = new TextField<String>();
		tfPasswd.setFieldLabel("Password");
		tfPasswd.setPassword(true);
		tfPasswd.setAllowBlank(false);
		loginForm.add(tfPasswd);
		
		final Button btn = new Button("Login");
		loginForm.addButton(btn);

		tfPasswd.addKeyListener(new KeyListener() {
			@Override
			public void componentKeyPress(ComponentEvent event) {
				super.componentKeyPress(event);
				if (event.getKeyCode() == KeyCodes.KEY_ENTER)
					btn.fireEvent(Events.Select);
			}
		});
		
		btn.addSelectionListener(new SelectionListener<ButtonEvent>() {
			@Override
			public void componentSelected(ButtonEvent ce) {
				if (!loginForm.isValid()) return;
				
				final String email = tfEmail.getValue();
				String encrypted = ClientUtil.md5(tfPasswd.getValue());
				dailyReview.showLoading();
				ClientUtil.getService().login(email, encrypted, new MsgCallback(dailyReview) {
					public void onSuccess(Result result) {
						clearLoading();
						tfPasswd.setRawValue(null);
						if (result.successful()) { // logged in successfully
							dailyReview.setEmail(email);
							try {
								dailyReview.setUserLevel(Integer.parseInt(result.getValue("userLevel")));
							} catch (NumberFormatException e) {
								GWT.log("Parsing user level failed! " + result.getValue("userLevel"), e);
								ClientUtil.alert("Loading your information failed!");
							}
							dailyReview.showOverview();
						} else if (Status.FAILURE == result.status)
							ClientUtil.alert("Login Failed: incorrect email or password");
						else if (Status.OTHER_A == result.status)
							ClientUtil.alert("Login Failed! Your account is locked.");
						else if (Status.OTHER_B == result.status)
							ClientUtil.info("You have to confirm your account before signing in");
						else super.onSuccess(result);
					}
				});
			}
		});
		
		// clear the password field when switching back to the login form
		loginForm.addListener(Events.Attach, new Listener<BaseEvent>() {
			@Override
			public void handleEvent(BaseEvent be) {
				tfPasswd.setRawValue(null);
			}
		});
		
	}

	private void initRegSubmitForm() {
		regSubmitForm = new FormPanel();
		regSubmitForm.setHeading(DailyReview.constants.regHeading());
		regSubmitForm.setWidth(340);
		regSubmitForm.setFrame(true);
		
		final TextField<String> tfEmail = new TextField<String>();
		tfEmail.setFieldLabel("Email");
		tfEmail.setAllowBlank(false);
		tfEmail.setRegex(ClientUtil.REG_EXP_EMAIL);
		tfEmail.getMessages().setRegexText("Invalid email format");
		regSubmitForm.add(tfEmail);
		
		regSubmitForm.addButton(new Button("Submit", new SelectionListener<ButtonEvent>() {
			@Override
			public void componentSelected(ButtonEvent ce) {
				if (!regSubmitForm.isValid()) return;
				
				final String email = tfEmail.getValue();
				final AsyncCallback<Result> acRegister = new MsgCallback(dailyReview) {
					private AsyncCallback<Result> instance = this;
					@Override
					public void onSuccess(Result result) {
						clearLoading();
						if (result.successful())
							dailyReview.showInfo("Success. Check your email to complete the registration.");
						else if (Status.VALUES_EXIST == result.status)
							dailyReview.showInfo("Failure. Email exists, maybe Forgot Password?");
						else if (Status.FAILURE == result.status) {
							MessageBox.confirm("Email submitted before",
									"Still submit to get a new confirmation link?",
									new Listener<MessageBoxEvent>() {
								@Override
								public void handleEvent(MessageBoxEvent be) {
									if ("Yes".equals(be.getButtonClicked().getText())) {
										dailyReview.showLoading();
										ClientUtil.getService().submitRegistration(email, true, instance);
									}
								}
							});
						} else super.onSuccess(result);
					}
				};
				dailyReview.showLoading();
				ClientUtil.getService().submitRegistration(email, false, acRegister);
			}
		}));
		
		regSubmitForm.addButton(new Button("Back to LOGIN", new SelectionListener<ButtonEvent>() {
			@Override
			public void componentSelected(ButtonEvent ce) {
				showLoginForm();
			}
		}));
	}
	
	public void initRegProcessForm(final String email, final String code) {
		regProcessForm = new FormPanel();
		regProcessForm.setHeading("Complete your registration");
		regProcessForm.setWidth(380);
		regProcessForm.setLabelWidth(120);
		regProcessForm.setFrame(true);
		
		final TextField<String> tfEmail = new TextField<String>();
		tfEmail.setFieldLabel("Email");
		tfEmail.setValue(email);
		tfEmail.setReadOnly(true);
		regProcessForm.add(tfEmail);
		
		final TextField<String> tfPasswd = new TextField<String>();
		tfPasswd.setFieldLabel("Password");
		tfPasswd.setPassword(true);
		tfPasswd.setAllowBlank(false);
		regProcessForm.add(tfPasswd);
		
		final TextField<String> tfPasswdAgain = new TextField<String>();
		tfPasswdAgain.setFieldLabel("Password Again");
		tfPasswdAgain.setPassword(true);
		tfPasswdAgain.setAllowBlank(false);
		regProcessForm.add(tfPasswdAgain);
		
		regProcessForm.addButton(new Button("Submit", new SelectionListener<ButtonEvent>() {
			@Override
			public void componentSelected(ButtonEvent ce) {
				if (!regProcessForm.isValid()) return;
				
				if (!tfPasswd.getValue().equals(tfPasswdAgain.getValue())) {
					ClientUtil.flash("Error: passwords inputed are not the same");
					return;
				}
				
				AsyncCallback<Result> acRegister = new MsgCallback(dailyReview) {
					@Override
					public void onSuccess(Result result) {
						clearLoading();
						if (result.successful()) { // registered and signed in
							dailyReview.setEmail(email);
							dailyReview.showOverview();
						} else if (Status.FAILURE == result.status)
							ClientUtil.alert("User does not exist. Register again if confirming too late.");
						else if (Status.OTHER_A == result.status)
							ClientUtil.alert("Your account is locked");
					}
				};
				String encrypted = ClientUtil.md5(tfPasswd.getValue());
				dailyReview.showLoading();
				ClientUtil.getService().setupUser(email, encrypted, acRegister);
			}
		}));
		
		regProcessForm.addButton(new Button("Cancel Registration", 
				new SelectionListener<ButtonEvent>() {
			@Override
			public void componentSelected(ButtonEvent ce) {
				// TODO delete the user's data from the database
				ClientUtil.info("Not implemented");
			}
		}));
	}
	
	private void initForgotPwdSubmitForm() {
		forgotPwdSubmitForm = new FormPanel();
		forgotPwdSubmitForm.setHeading("Forgot Password");
		forgotPwdSubmitForm.setWidth(340);
		forgotPwdSubmitForm.setFrame(true);
		
		final TextField<String> tfEmail = new TextField<String>();
		tfEmail.setFieldLabel("Your Email");
		tfEmail.setAllowBlank(false);
		tfEmail.setRegex(ClientUtil.REG_EXP_EMAIL);
		tfEmail.getMessages().setRegexText("Invalid email format");
		forgotPwdSubmitForm.add(tfEmail);
		
		forgotPwdSubmitForm.addButton(new Button("Submit", new SelectionListener<ButtonEvent>() {
			@Override
			public void componentSelected(ButtonEvent ce) {
				if (!forgotPwdSubmitForm.isValid()) return;
				
				final String email = tfEmail.getValue();
				final AsyncCallback<Result> acSubmit = new MsgCallback(dailyReview) {
					@Override
					public void onSuccess(Result result) {
						clearLoading();
						if (result.successful())
							dailyReview.showInfo("Success. Check your email to reset password.");
						else if (Status.FAILURE == result.status)
							ClientUtil.alert("Failure. Accout does not exist, register first?");
						else if (Status.OTHER_A == result.status)
							ClientUtil.info("Your account is locked.");
						else super.onSuccess(result);
					}
				};
				dailyReview.showLoading();
				ClientUtil.getService().submitForgotPasswd(email, acSubmit);
			}
		}));
		
		forgotPwdSubmitForm.addButton(new Button("Back to LOGIN", new SelectionListener<ButtonEvent>() {
			@Override
			public void componentSelected(ButtonEvent ce) {
				showLoginForm();
			}
		}));
	}
	
	private void showForm(FormPanel form) {
		configOptions();
		dailyReview.showPanel(form, 50);
	}
	
	private void showLoginForm() {
		if (loginForm == null) initLoginForm();
		showForm(loginForm);
	}
	
}
